--[[------------------------------------------------------------------------------------------------------
oUF_AuraWatch by Astromech
Please leave comments, suggestions, and bug reports on this addon's WoWInterface page

To setup, create a table named AuraWatch in your unit frame. There are several options
you can specify, as explained below.
	
	icons 
		Mandatory!
		A table of frames to be used as icons. oUF_Aurawatch does not position
		these frames, so you must do so yourself. Each icon needs a spellID entry,
		which is the spell ID of the aura to watch. Table should be set up
		such that values are icon frames, but the keys can be anything.
		
		Note each icon can have several options set as well. See below.
	strictMatching
		Default: false
		If true, AuraWatch will only show an icon if the specific aura
		with the specified spell id is on the unit. If false, AuraWatch
		will show the icon if any aura with the same name and icon texture
		is on the unit. Strict matching can be undesireable because most
		ranks of an aura have different spell ids.
	missingAlpha
		Default 0.75
		The alpha value for icons of auras which have faded from the unit.
	presentAlpha
		Default 1
		The alpha value for icons or auras present on the unit.
	onlyShowMissing
		Default false
		If this is true, oUF_AW will hide icons if they are present on the unit.
	onlyShowPresent
		Default false
		If this is true, oUF_AW will hide icons if they have expired from the unit.
	customIcons
		Default false
		If this is true, oUF_AW will not create any frames or textures for your
		icons. It assumes you will handle it.
	fromUnits
		Default {["player"] = true, ["pet"] = true, ["vehicle"] = true}
		A table of units from which auras can originate. Have the units be the keys
		and "true" be the values.
	anyUnit
		Default false
		Set to true for oUF_AW to to show an aura no matter what unit it 
		originates from. This will override any fromUnits setting.
	PostCreateIcon
		Default nil
		A function to call when an icon is created to modify it, such as adding
		a border or repositioning the count fontstring. Leave as nil to ignore.
		The arguements are: AuraWatch table, icon, auraSpellID, auraName, unitFrame
	PostResetIcon
		Default nil
		A function to call when an icon is reset, that is an aura has been applied
		or refreshed. This is passed the AuraWatch frame, the icon, aura stack count,
		duration, and remaining time, as per a UnitAura call.
	PostExpireIcon
		Default nil
		A function to call when an icon is expired, that is an aura has disappeared.
		This is passed the AuraWatch frame and the icon.
	OverrideResetIcon
		Default nil
		Provide this function to handle the ResetIcon process yourself.
	OverrideExpireIcon
		Default nil
		Provide this function to handle the ExpireIcon process yourself.

Below are options set on a per icon basis. Set these as fields in the icon frames.

The following settings can be overridden from the AuraWatch table on a per-aura basis:
	onlyShowMissing
	onlyShowPresent
	fromUnits
	anyUnit
		
The following settings are unique to icons:
	
	spellID
		Mandatory!
		The spell id of the aura, as explained above.
	
Here is an example of how to set oUF_AW up:

	local createAuraWatch = function(self, unit)
		local auras = {}
		
		-- A table of spellIDs to create icons for
		-- To find spellIDs, look up a spell on www.wowhead.com and look at the URL
		-- http://www.wowhead.com/?spell=SPELL_ID
		local spellIDs = { ... }
		
		auras.presentAlpha = 1
		auras.missingAlpha = .7
		auras.PostCreateIcon = myCustomIconSkinnerFunction
		-- Set any other AuraWatch settings
		auras.icons = {}
		for i, sid in pairs(spellIDs) do
			local icon = CreateFrame("Frame", nil, self)
			icon.spellID = sid
			-- set the dimensions and positions
			icon:SetWidth(24)
			icon:SetHeight(24)
			icon:SetPoint("BOTTOM", self, "BOTTOM", 0, 28 * i)
			auras.icons[sid] = icon
			-- Set any other AuraWatch icon settings
		end
		self.AuraWatch = auras
	end
-----------------------------------------------------------------------------------------------------------]]

local _, ns = ...
local oUF = ns.oUF or _G.oUF
assert(oUF, "oUF_AuraWatch cannot find an instance of oUF. If your oUF is embedded into a layout, it may not be embedded properly.")

local UnitAura, UnitGUID = UnitAura, UnitGUID
local GUIDs = {}

local PLAYER_UNITS = {
	player = true,
	vehicle = true,
	pet = true,
}

local SetupGUID
do 
	local cache = setmetatable({}, {__type = "k"})

	local frame = CreateFrame"Frame"
	frame:SetScript("OnEvent", function(self, event)
		for k,t in pairs(GUIDs) do
			GUIDs[k] = nil
			wipe(t)
			cache[t] = true
		end
	end)
	frame:RegisterEvent"PLAYER_REGEN_ENABLED"
	frame:RegisterEvent"PLAYER_ENTERING_WORLD"
	
	function SetupGUID(guid)
		local t = next(cache)
		if t then
			cache[t] = nil
		else
			t = {}
		end
		GUIDs[guid] = t
	end
end

local function DefaultResetIcon(watch, icon, count, duration, remaining)
	if not icon.onlyShowMissing then
		if icon.cd then
			if duration and duration > 0 then
				icon.cd:SetCooldown(remaining - duration, duration)
				icon.cd:Show()
			else
				icon.cd:Hide()
			end
		end
		if icon.count then
			icon.count:SetText(count > 1 and count)
		end
		if icon.overlay then
			icon.overlay:Hide()
		end
		icon:SetAlpha(watch.presentAlpha)
		icon:Show()
		if watch.PostResetIcon then watch.PostResetIcon(watch, icon) end
	end
end

local function ResetIcon(watch, icon, ...)
	if watch.OverrideResetIcon then
		watch.OverrideResetIcon(watch, icon, ...)
	else
		DefaultResetIcon(watch, icon, ...)
	end
end

local function DefaultExpireIcon(watch, icon)
	if not icon.onlyShowPresent then
		if icon.cd then 
			icon.cd:Hide() 
		end
		if icon.count then 
			icon.count:SetText() 
		end
		icon:SetAlpha(watch.missingAlpha)
		if icon.overlay then
			icon.overlay:Show()
		end
		icon:Show()
		if watch.PostExpireIcon then watch.PostExpireIcon(watch, icon) end
	end
end

local function ExpireIcon(watch, icon, ...)
	if watch.OverrideExpireIcon then
		watch.OverrideExpireIcon(watch, icon, ...)
	else
		DefaultExpireIcon(watch, icon, ...)
	end
end

local Update
do
	local found = {}
	function Update(frame, event, unit)
		if frame.unit ~= unit then return end
		local watch = frame.AuraWatch
		local index, icons = 1, watch.watched
		local _, name, texture, count, duration, remaining, caster, key, icon, spellid 
		local filter = "HELPFUL"
		local guid = UnitGUID(unit)
		if not GUIDs[guid] then SetupGUID(guid) end
		
		for key, icon in pairs(icons) do
			icon:Hide()
		end
		
		while true do
			name, _, texture, count, _, duration, remaining, caster, _, _, spellid = UnitAura(unit, index, filter)
			if not name then 
				if filter == "HELPFUL" then
					filter = "HARMFUL"
					index = 1
				else
					break
				end
			else
				if watch.strictMatching then
					key = spellid
				else
					key = name..texture
				end
				icon = icons[key]
				if icon and (icon.anyUnit or (caster and icon.fromUnits[caster])) then
					ResetIcon(watch, icon, count, duration, remaining)
					GUIDs[guid][key] = true
					found[key] = true
				end
				index = index + 1
			end
		end
		
		for key in pairs(GUIDs[guid]) do
			if icons[key] and not found[key] then
				ExpireIcon(watch, icons[key])
			end
		end
		
		wipe(found)
	end
end

local function SetupIcons(self)

	local watch = self.AuraWatch
	local icons = watch.icons
	watch.watched = {}
	if not watch.missingAlpha then watch.missingAlpha = 0.75 end
	if not watch.presentAlpha then watch.presentAlpha = 1 end
	
	for _,icon in pairs(icons) do
	
		local name, _, image = GetSpellInfo(icon.spellID)
		if not name then error("oUF_AuraWatch error: no spell with "..tostring(icon.spellID).." spell ID exists") end
		icon.name = name
	
		if not watch.customIcons then
			local cd = CreateFrame("Cooldown", nil, icon)
			cd:SetAllPoints(icon)
			icon.cd = cd

			local tex = icon:CreateTexture(nil, "BACKGROUND")
			tex:SetAllPoints(icon)
			tex:SetTexture(image)
			icon.icon = tex
			
			local overlay = icon:CreateTexture(nil, "OVERLAY")
			overlay:SetTexture"Interface\\Buttons\\UI-Debuff-Overlays"
			overlay:SetAllPoints(icon)
			overlay:SetTexCoord(.296875, .5703125, 0, .515625)
			overlay:SetVertexColor(1, 0, 0)
			icon.overlay = overlay

			local count = icon:CreateFontString(nil, "OVERLAY")
			count:SetFontObject(NumberFontNormal)
			count:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 0)
			icon.count = count
		end

		if icon.onlyShowMissing == nil then
			icon.onlyShowMissing = watch.onlyShowMissing
		end
		if icon.onlyShowPresent == nil then
			icon.onlyShowPresent = watch.onlyShowPresent
		end
		if icon.fromUnits == nil then
			icon.fromUnits = watch.fromUnits or PLAYER_UNITS
		end
		if icon.anyUnit == nil then
			icon.anyUnit = watch.anyUnit
		end
		
		if watch.strictMatching then
			watch.watched[icon.spellID] = icon
		else
			watch.watched[name..image] = icon
		end

		if watch.PostCreateIcon then watch:PostCreateIcon(icon, icon.spellID, name, self) end
	end
end

local function ForceUpdate(element)
	return Update(element.__owner, 'ForceUpdate', element.__owner.unit)
end

local function Enable(self)
	if self.AuraWatch then
		self.AuraWatch.__owner = self
		self.AuraWatch.ForceUpdate = ForceUpdate
		
		self:RegisterEvent("UNIT_AURA", Update)
		SetupIcons(self)
		return true
	else
		return false
	end
end

local function Disable(self)
	if self.AuraWatch then
		self:UnregisterEvent("UNIT_AURA", Update)
		for _,icon in pairs(self.AuraWatch.icons) do
			icon:Hide()
		end
	end
end

oUF:AddElement("AuraWatch", Update, Enable, Disable)
